package com.ikas.ai.server.module.assessment.service;

import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageInfo;
import com.ikas.ai.enums.redis.RedisKeyEnum;
import com.ikas.ai.model.PageQuery;
import com.ikas.ai.server.error.BusinessException;
import com.ikas.ai.server.module.appraise.enums.AssessStatusEnum;
import com.ikas.ai.server.module.appraise.enums.StatusEnum;
import com.ikas.ai.server.module.appraise.enums.TriggerTypeEnum;
import com.ikas.ai.server.module.assessment.dao.AssessModelMapper;
import com.ikas.ai.server.module.assessment.enums.TriggerFromEnum;
import com.ikas.ai.server.module.assessment.model.dto.*;
import com.ikas.ai.server.module.assessment.model.po.AssessModel;
import com.ikas.ai.server.module.assessment.model.po.AssessWfResult;
import com.ikas.ai.server.module.assessment.model.po.TimeLag;
import com.ikas.ai.server.module.assessment.model.vo.AssessModelVo;
import com.ikas.ai.server.module.cockpit.model.po.SimplifyRealDataHis;
import com.ikas.ai.server.module.flow.model.Flow;
import com.ikas.ai.server.module.flow.service.FlowNodeLineService;
import com.ikas.ai.server.module.flow.service.FlowNodeService;
import com.ikas.ai.server.module.flow.service.FlowService;
import com.ikas.ai.server.schedule.task.AssessModelTaskService;
import com.ikas.ai.utils.DateTimeUtil;
import com.ikas.ai.utils.PageExUtil;
import com.ikas.ai.utils.RedisUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * <p>
 * 工况模型评价表 服务实现类
 * </p>
 *
 * @author wuyf
 * @since 2023-10-26
 */
@Slf4j
@Service
public class AssessModelService extends ServiceImpl<AssessModelMapper, AssessModel> {

    @Resource
    private AssessWfResultService assessWfResultService;

    @Resource
    private AssessModelTaskService assessModelTaskService;

    @Resource
    private FlowService flowService;

    @Resource
    private FlowNodeService flowNodeService;

    @Resource
    private FlowNodeLineService flowNodeLineService;


    public PageInfo<AssessModelVo> pageAm(AmQueryDto queryDto, PageQuery page) {
        PageExUtil.configPageHelper(page);
        List<AssessModel> list = queryWrapperList(queryDto);
        return PageExUtil.convert(new PageInfo<>(list), this::coverToAmVo);
    }


    public List<AssessModelVo> listAm(AmQueryDto queryDto) {
        List<AssessModel> list = queryWrapperList(queryDto);
        return list.stream().map(this::coverToAmVo).collect(Collectors.toList());
    }

    private List<AssessModel> queryWrapperList(AmQueryDto queryDto) {
        LambdaQueryWrapper<AssessModel> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(queryDto.getName()), AssessModel::getName, queryDto.getName());
        queryWrapper.like(StringUtils.isNotBlank(queryDto.getMeteName()), AssessModel::getMeteName, queryDto.getMeteName());
        queryWrapper.eq(StringUtils.isNotBlank(queryDto.getWfName()), AssessModel::getWfName, queryDto.getWfName());
        queryWrapper.eq(StringUtils.isNotBlank(queryDto.getMachineNo()), AssessModel::getMachineNo, queryDto.getMachineNo());
        queryWrapper.eq(queryDto.getStatus() != null, AssessModel::getStatus, queryDto.getStatus());
        queryWrapper.orderByAsc(AssessModel::getMachineNo).orderByDesc(AssessModel::getUpdateTime);
        return this.list(queryWrapper);
    }

    /**
     * 实体转VO
     *
     * @param am 实体类
     * @return vo of am
     */
    private AssessModelVo coverToAmVo(AssessModel am) {
        AssessModelVo amVo = BeanUtil.copyProperties(am, AssessModelVo.class);
        //触发类型描述
        String triggerTypeDesc = TriggerTypeEnum.getDescByValue(am.getTriggerType());
        amVo.setTriggerName(String.join(",", am.getMeteName(), triggerTypeDesc));
        return amVo;
    }


    @Transactional(rollbackFor = Exception.class)
    public boolean addAm(AmAddDto amAddDto) {
        AssessModel existsAm = getAmByMeteCode(amAddDto.getMachineNo(), amAddDto.getMeteCode(), amAddDto.getTriggerType());
        if (existsAm != null) {
            throw new BusinessException("已存在相同触发条件的测点评价模型");
        }
        AssessModel am = BeanUtil.copyProperties(amAddDto, AssessModel.class);
        return this.save(am);
    }


    @Transactional(rollbackFor = Exception.class)
    public boolean updateAm(AmUpdateDto amUpdateDto) {
        AssessModel am = this.getById(amUpdateDto.getId());
        if (am == null) {
            throw new BusinessException("没找到可修改的评价模型");
        }
        if (StatusEnum.VALID.getCode().equals(am.getStatus())) {
            throw new BusinessException("当前评价模型已生效，不能修改");
        }
        AssessModel existsAm = getAmByMeteCodeExcludeUpId(amUpdateDto);
        if (existsAm != null) {
            throw new BusinessException("已存在相同触发条件的测点评价模型");
        }
        AssessModel newAm = BeanUtil.copyProperties(amUpdateDto, AssessModel.class);
        //把缓存的原key删除(不管redis中key存不存在，就是删)
        RedisUtils.del(RedisKeyEnum.ASSESS_MODEL_TRIGGER_KEY.key(am.getMachineNo(), am.getMeteCode(), am.getTriggerType()));
        return this.updateById(newAm);
    }

    /**
     * 获取评价模型通过meteCode，且排除upId
     *
     * @param amUpdateDto 入参对象
     * @return AssessModel
     */
    private AssessModel getAmByMeteCodeExcludeUpId(AmUpdateDto amUpdateDto) {
        LambdaQueryWrapper<AssessModel> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AssessModel::getMachineNo, amUpdateDto.getMachineNo());
        queryWrapper.eq(AssessModel::getMeteCode, amUpdateDto.getMeteCode());
        queryWrapper.eq(AssessModel::getTriggerType, amUpdateDto.getTriggerType());
        queryWrapper.ne(AssessModel::getId, amUpdateDto.getId());
        return this.getOne(queryWrapper);
    }


    @Transactional(rollbackFor = Exception.class)
    public boolean delAm(Long id) {
        AssessModel am = this.getById(id);
        if (am == null) {
            throw new BusinessException("没有找到需要删除的评价模型");
        }
        if (am.getStatus().equals(StatusEnum.VALID.getCode())) {
            throw new BusinessException("生效状态，不能删除");
        }
        // 同时删除流程、流程节点、流程连线相关数据
        Flow flow = flowService.getFlowBySceneId(id);
        if (flow != null) {
            Long flowId = flow.getId();
            flowService.removeById(flowId);
            flowNodeService.deleteFlowNodesByFlowId(flowId);
            flowNodeLineService.deleteFlowLine(flowId);
        }
        //把缓存的原key删除(不管redis中key存不存在，就是删)
        RedisUtils.del(RedisKeyEnum.ASSESS_MODEL_TRIGGER_KEY.key(am.getMachineNo(), am.getMeteCode(), am.getTriggerType()));
        return this.removeById(id);
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean delBatchAm(List<Long> ids) {
        List<AssessModel> assessModels = this.listByIds(ids);
        List<AssessModel> validCollect = assessModels.stream().filter(model -> model.getStatus().equals(StatusEnum.VALID.getCode())).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(validCollect)) {
            throw new BusinessException("包含生效状态的评价模型，不能删除");
        }
        // 同时删除流程、流程节点、流程连线相关数据
        assessModels.forEach(model -> {
            Long SceneId = model.getId();
            // 同时删除流程、流程节点、流程连线相关数据
            Flow flow = flowService.getFlowBySceneId(SceneId);
            if (flow != null) {
                Long flowId = flow.getId();
                flowService.removeById(flowId);
                flowNodeService.deleteFlowNodesByFlowId(flowId);
                flowNodeLineService.deleteFlowLine(flowId);
            }
        });
        Set<String> redisKeyList = assessModels.stream().map(model ->
                RedisKeyEnum.ASSESS_MODEL_TRIGGER_KEY.key(
                        model.getMachineNo(), model.getMeteCode(), model.getTriggerType())
        ).collect(Collectors.toSet());
        //把缓存的原key删除(不管redis中key存不存在，就是删)
        RedisUtils.del(redisKeyList);
        return this.removeByIds(ids);
    }


    @Transactional(rollbackFor = Exception.class)
    public boolean enableAm(Long id) {
        AssessModel am = this.getById(id);
        if (am == null) {
            throw new BusinessException("没有找到对应的评价模型");
        }
        AssessModel newAm = new AssessModel();
        newAm.setId(am.getId());
        //异或运算一下
        newAm.setStatus(am.getStatus() ^ 1);
        //把缓存的原key删除(不管redis中key存不存在，就是删)
        RedisUtils.del(RedisKeyEnum.ASSESS_MODEL_TRIGGER_KEY.key(am.getMachineNo(), am.getMeteCode(), am.getTriggerType()));
        return this.updateById(newAm);
    }


    public AssessModel detailAm(Long id) {
        return this.getById(id);
    }

    /**
     * 触发模型
     */
    public void wakeAssessByConditionDetail(String meteCode, String machineNo) {
        AmTriggerDto amTriggerDto = new AmTriggerDto()
                .setBatch(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"))
                .setMachineNo(machineNo)
                .setMeteCode(meteCode)
                //来源：启停仿真
                .setTriggerFrom(TriggerFromEnum.START_STOP_SIMULATED.getValue());
        try {
            triggerModel(amTriggerDto);
        } catch (Exception e) {
            log.error("lltrigger assess model error:{}", e.getMessage());
        }
    }

    public boolean checkModelExist(String machineNo, String meteCode) {
        AssessModel am = RedisUtils.selectFromCacheOrDb(
                RedisKeyEnum.ASSESS_MODEL_TRIGGER_KEY.key(machineNo, meteCode, 1),
                () -> getAmByMeteCode(machineNo, meteCode, 1)
        );

        return am != null;
    }


    public boolean triggerModel(AmTriggerDto triggerDto) {
        Assert.notBlank(triggerDto.getMachineNo(), "机组编号不能为空");
        Assert.notBlank(triggerDto.getMeteCode(), "测点编号不能为空");
        //尝试先从缓存获取评价模型信息
        AssessModel am = RedisUtils.selectFromCacheOrDb(
                RedisKeyEnum.ASSESS_MODEL_TRIGGER_KEY.key(triggerDto.getMachineNo(), triggerDto.getMeteCode(), 1),
                () -> getAmByMeteCode(triggerDto.getMachineNo(), triggerDto.getMeteCode(), 1)
        );
        if (am == null) {
            throw new BusinessException("未找到可用的评价模型");
        }
        if (!StatusEnum.VALID.getCode().equals(am.getStatus())) {
            throw new BusinessException("当前触发的模型还未生效");
        }
        //异步调用
        CompletableFuture.supplyAsync(() ->
                //1.保存最开始的评价结果
                saveStartOfAssessWfResult(triggerDto, am)
        ).thenAccept(assessWfResults ->
                //2.开启评价模型流程
                assessModelTaskService.execModelFlow(assessWfResults)
        );
        return true;
    }

    /**
     * 获取一条评价模型-通过meteCode
     *
     * @param machineNo   机组编号
     * @param meteCode    测点编号
     * @param triggerType 触发类型
     * @return AssessModel
     */
    private AssessModel getAmByMeteCode(String machineNo, String meteCode, int triggerType) {
        LambdaQueryWrapper<AssessModel> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AssessModel::getMachineNo, machineNo);
        queryWrapper.eq(AssessModel::getMeteCode, meteCode);
        queryWrapper.eq(AssessModel::getTriggerType, triggerType);
        return this.getOne(queryWrapper);
    }


    /**
     * 保存最开始的评价结果
     *
     * @param triggerDto dto参数对象
     * @param am         模型
     */
    @SneakyThrows
    private AssessWfResult saveStartOfAssessWfResult(AmTriggerDto triggerDto, AssessModel am) {
        AssessWfResult awfResult = BeanUtil.copyProperties(triggerDto, AssessWfResult.class);
        awfResult.setModelId(am.getId());
        awfResult.setModelName(am.getName());
        awfResult.setStatus(AssessStatusEnum.NOT_START.getStatus());
        awfResult.setMeteName(am.getMeteName());
        assessWfResultService.save(awfResult);
        return awfResult;
    }


    @SneakyThrows

    public Double importTimeFileToCalAvgTime(MultipartFile file) {
        InputStream inputStream = file.getInputStream();
        return handleFileWithCondition(inputStream);
    }

    private Double handleFileWithCondition(InputStream inputStream) throws Exception {
        ImportParams importParams = new ImportParams();
        //设置从第几个（索引）sheet开始
        importParams.setStartSheetIndex(0);
        importParams.setSheetNum(1);
        importParams.setHeadRows(1);
        List<TimeLag> timeLags = ExcelImportUtil.importExcel(inputStream,
                TimeLag.class, importParams);
        return calculateTimeLag(timeLags);
    }

    /**
     * 计算时间差
     *
     * @param timeLags 入参对象
     * @return double
     */
    private Double calculateTimeLag(List<TimeLag> timeLags) {
        List<Long> timeLagList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(timeLags)) {
            for (TimeLag timeLag : timeLags) {
                if (StringUtils.isBlank(timeLag.getTime1()) || StringUtils.isBlank(timeLag.getTime2())) {
                    continue;
                }
                long time1 = Objects.requireNonNull(DateTimeUtil.getDateTimeS(timeLag.getTime1(), true)).getTime();
                long time2 = Objects.requireNonNull(DateTimeUtil.getDateTimeS(timeLag.getTime2(), true)).getTime();
                timeLagList.add(Math.abs(time2 - time1));
                log.info("【{}】-【{}】= {}", timeLag.getTime2(), timeLag.getTime1(), (time2 - time1));
            }
        }
        OptionalDouble average = timeLagList.stream().mapToLong(Long::longValue)
                .average();
        double result = average.orElse(0.0);
        return Math.round(result * 100.0) / 100.0;
    }

    /**
     * 计算执行项和响应项测点平均时间差
     *
     * @param calDto   入参对象
     * @param execList 执行项数据
     * @param respList 响应项数据
     * @return Double
     */

    public Double getAvgTimeByMeteCodes(MeteDataHisCalDto calDto,
                                        List<SimplifyRealDataHis> execList,
                                        List<SimplifyRealDataHis> respList) {
        //时间范围 单位分钟
        Integer timeRangeOfGt = calDto.getTimeRangeOfGt();
        List<TimeLag> timeLags = new ArrayList<>();
        //是否去掉最近一条数据，true时最近一条不参与计算平均值
        if (Boolean.TRUE.equals(calDto.getDelTheLastOneData() && execList != null) && execList.size() > 1) {
            execList.remove(0);
        }
        assert execList != null;
        execList.forEach(exec -> {
            TimeLag timeLag = new TimeLag();
            long execTime1 = exec.getIdtime();
            long execTime2 = execTime1 + timeRangeOfGt * 60 * 1000;
            respList.forEach(resp -> {
                long respTime = resp.getIdtime();
                if (execTime1 < respTime && respTime < execTime2) {
                    timeLag.setTime1(DateTimeUtil.getMillTimeString(execTime1));
                    timeLag.setTime2(DateTimeUtil.getMillTimeString(respTime));
                    timeLags.add(timeLag);
                }
            });
        });
        log.info("匹配上的数据条数{}", timeLags.size());
        return calculateTimeLag(timeLags);
    }

}
